//-------------------------------------------------------------------
//	Copyright (c) 2012-2014 Zhirnov Andrey
//	This file is part of the "UXGEN-ES" project.
//	See copyright notice in "Engine.h".
//-------------------------------------------------------------------

#pragma once

#include "Graphics/Resources/Font.h"
#include "Graphics/Systems/BatchRender.h"
#include "Graphics/Systems/Viewport.h"
#include "Graphics/Primitives/RectPrimitives.h"

namespace Engine
{
namespace Graphics
{
	typedef Primitives::Rect2D		FontRect2D;

	
/*
=================================================
	IsNewLine
=================================================
*/
	inline bool IsNewLine (uni_c_string text, usize &i)
	{
		// 13 10 or 10

		char	curr = text[i];
		char	next = i < text.Length() ? text[i+1] : 0;

		return curr == 10 or (curr == 13 and next == 10 and ++i);
	}
	
/*
=================================================
	CanWrapCurrent
----
	returns true if you can wrap current char to new line
=================================================
*
	inline bool CanWrapCurrent (uni_c_string text, usize i)
	{
		const char	c = text[i];
		const char	p = i > 0 ? text[i-1] : 0;
		const char	n = i < text.Length() ? text[i+1] : 0;

		// ...
		/*if ( c == '.' and n != '.' )
			return true;

		return	c == ',' or c == ';' or c == ':' or
				c == ')' or c == '}' or c == '>' or
				c == '?' or c == '!' or c == '-' or
				n == '"' or n == ' ' or n == '	';
		return c == ' ' or c == '	' or p == '-';
	}
	
/*
=================================================
	GetNextSymbol
=================================================
*/
	inline usize GetNextSymbol (uni_c_string text, usize i)
	{
		for (; i < text.Length(); ++i)
		{
			const char	c = text[i];

			if ( c != /*space*/' ' and c != /*tab*/'	' )
				break;
		}
		return i;
	}
	
/*
=================================================
	LineAlign
=================================================
*/
	inline void LineAlign (ext_string &formattedText, ext_string::TReader &pos, float maxLineWidth,
						  float lineWidth, int align, float scaleX)
	{
		// TODO: use text direction

		if ( EnumCmp( align, ext_string::EAlign::RIGHT ) )
			formattedText.InsertOffsetX( pos, (maxLineWidth - lineWidth) * scaleX );
		else
		if ( EnumCmp( align, ext_string::EAlign::CENTER_X ) )
			formattedText.InsertOffsetX( pos, (maxLineWidth - lineWidth) * 0.5f * scaleX );
	}

/*
=================================================
	_Draw
=================================================
*/
	template <typename GlyphReader>
	inline
	void IFont::_Draw (	const ViewportPtr &vp,
						const BatchRenderPtr &br,
						const GlyphReader &glyphReader,
						const FontBatch &params,
						const rect &region,
						uni_c_string text,
						bool scissor )
	{
		glyphReader.ScaleChanged( params._scale );

		vec2		k			= vp->ViewRect().GetSize().y * params._scale * vp->GetResCorrectAspect();
		float const	base_line	= glyphReader.GetBaseLineTopMargin() * k.y;
		float const	hor_spacing	= k.y * (1.0f + params._spacing);
		vec2		line		= vec2( region.left, region.top - base_line );
		FontRect2D	quad;

		bvec2 const	dir			= ivec2( params._direction & ETextDirection::_HORIZONTAL_MASK,
										 params._direction & ETextDirection::_VERTICAL_MASK ) ==
								  ivec2( ETextDirection::LEFT_TO_RIGHT, ETextDirection::TOP_TO_BOTTOM );
		int	const	left_idx	= 2 - dir.x * 2;
		int	const	right_idx	= dir.x * 2;

		line.x = dir.x ?  region.left				: region.right;
		line.y = dir.y ?  region.top - base_line	: region.bottom + k.y - base_line;

		k.x = SetSign( k.x, not dir.x );

		for (usize i = 0; i < text.Length(); ++i)
		{
			uint8 j = (uint8)text[i];

			if ( IsNewLine( text, i ) )
			{
				line.x  = region[ left_idx ];
				line.y += SetSign( hor_spacing, dir.y );
				continue;
			}

			if ( not glyphReader.Load( j ) )
			{
				WARNING( "Char is out of range!" );
				continue;
			}

			glyphReader.SetMaterial( br, params.GetMaterial() );

			Glyph const &	glyph = glyphReader.Get( j );

			quad.position[ left_idx ]	= line.x + glyph.offsetX[0] * k.x;
			quad.position[ right_idx ]	= quad.position[ left_idx ] + glyph.size.x * k.x;
			quad.position.bottom		= line.y - (glyph.size.y - glyph.offsetY) * k.y;
			quad.position.top			= line.y + glyph.offsetY * k.y;
			quad.texcoord				= glyph.texcoord;
			quad.color					= params._color;
			
			// if glyph will drawn outside of region
			if ( scissor and not quad.position.IntersectRect( region ) )
				continue;

			if ( not glyph.empty )
				br->AddBatch( quad );
			
			line.x = quad.position[ right_idx ] + glyph.offsetX[1] * k.x;
		}
	}
	
/*
=================================================
	_Draw
=================================================
*/
	template <typename GlyphReader>
	inline
	void IFont::_Draw (	const ViewportPtr &vp,
						const BatchRenderPtr &br,
						const GlyphReader &glyphReader,
						const FontBatch &params,
						const rect &region,
						const ext_string &extText,
						bool scissor )
	{
		glyphReader.ScaleChanged( params._scale );

		vec2 const	base_scale	= vp->ViewRect().GetSize().y * params._scale * vp->GetResCorrectAspect();
		vec2		k			= base_scale;
		float		spacing		= params._spacing;
		float		line_spacing= MaxValue<float>();
		float const	base_line	= glyphReader.GetBaseLineTopMargin() * k.y;
		float		hor_spacing	= k.y * (1.0f + spacing);
		vec2		line		= vec2( region.left, region.top - base_line );
		FontRect2D	quad;
		
		bvec2 const	dir			= ivec2( params._direction & ETextDirection::_HORIZONTAL_MASK,
										 params._direction & ETextDirection::_VERTICAL_MASK ) ==
								  ivec2( ETextDirection::LEFT_TO_RIGHT, ETextDirection::TOP_TO_BOTTOM );
		int	const	left_idx	= 2 - dir.x * 2;
		int	const	right_idx	= dir.x * 2;

		line.x = dir.x ?  region.left				: region.right;
		line.y = dir.y ?  region.top - base_line	: region.bottom + k.y - base_line;

		k.x = SetSign( k.x, not dir.x );

		quad.color = params._color;

		ext_string::TReader		reader	= extText.GetReader();

		while ( reader.NextChunk() != ext_string::END )
		{
			switch ( reader.GetState() )
			{
				case ext_string::COLOR : {
					const color4u	new_color = ColorUtils::FromRGBA8<uint8>( reader.GetChunkInt() );
					quad.color = params._mulWithTextColor ?
									color4u( color4f(new_color) * color4f(params._color) ) :
									new_color;
					break;
				}
				
				case ext_string::SCALE : {
					float new_scale = reader.GetChunkFloat();
					//ASSERT_EXT( new_scale <= 1.0f + line_spacing, "Incompatible scale and spacing!" );

					k			= base_scale * new_scale;
					hor_spacing	= k.y * (1.0f + spacing);
					glyphReader.ScaleChanged( params._scale * new_scale );
					break;
				}
				case ext_string::SPACING :
					spacing		= reader.GetChunkFloat();
					hor_spacing = k.y * (1.0f + spacing);
					break;

				case ext_string::OFFSET_X :
					line.x += reader.GetChunkFloat() * k.x;
					break;

				case ext_string::OFFSET_Y :
					line.y += SetSign( reader.GetChunkFloat(), dir.y ) * k.y;
					break;

				case ext_string::STRING :
				{
					uni_string	text = reader.GetString();
					
					for (usize i = 0; i < text.Length(); ++i)
					{
						uint8 j = (uint8)text[i];
			
						// new line
						//if ( j == 13 or ( j == 10 and
						//	( i == 0 or ( i > 0 and text[i-1] != 13 ) ) ) )
						if ( IsNewLine( text, i ) )
						{
							line.x		  = region[ left_idx ];
							line.y		 += SetSign( hor_spacing, dir.y );
							line_spacing  = spacing;
							continue;
						}

						if ( not glyphReader.Load( j ) )
						{
							WARNING( "Char is out of range!" );
							continue;
						}

						glyphReader.SetMaterial( br, params.GetMaterial() );

						Glyph const &	glyph = glyphReader.Get( j );
			
						quad.position[ left_idx ]	= line.x + glyph.offsetX[0] * k.x;
						quad.position[ right_idx ]	= quad.position[ left_idx ] + glyph.size.x * k.x;
						quad.position.bottom		= line.y - (glyph.size.y - glyph.offsetY) * k.y;
						quad.position.top			= line.y + glyph.offsetY * k.y;
						quad.texcoord				= glyph.texcoord;
			
						// if glyph will drawn outside of region
						if ( scissor and not quad.position.IntersectRect( region ) )
							continue;

						if ( not glyph.empty )
							br->AddBatch( quad );
			
						line.x = quad.position[ right_idx ] + glyph.offsetX[1] * k.x;
					}
					break;
				}

				case ext_string::ALIGN :
					WARNING( "align not supported for drawing, only for formatting text!" );
					break;

				default :
					WARNING( "Unknown chunk!" );
			};
		}
	}
	
/*
=================================================
	_GetTextDimensions
=================================================
*/
	template <typename GlyphReader>
	inline
	void IFont::_GetTextDimensions (const ViewportPtr &viewport,
									const GlyphReader &glyphReader,
									const FontBatch &params,
									uni_c_string text,
									OUT vec2 &dim,
									OUT usize &lineLength)
	{
		glyphReader.ScaleChanged( params._scale );

		vec2 const	k			= viewport->ViewRect().GetSize().y * params._scale * viewport->GetResCorrectAspect();
		float const	base_line	= glyphReader.GetBaseLineTopMargin() * k.y;

		dim			= vec2();;
		lineLength	= 0;

		for (usize i = 0; i < text.Length(); ++i)
		{
			uint8 j = (uint8)text[i];
			
			if ( IsNewLine( text, i ) )
				return;
			
			if ( not glyphReader.Load( j ) )
			{
				WARNING( "Char is out of range!" );
				continue;
			}

			Glyph const & glyph = glyphReader.Get( j );
			
			dim.x += ( glyph.offsetX[0] + glyph.size.x + glyph.offsetX[1] ) * k.x;
			dim.y = Max( dim.y, -base_line + glyph.offsetY * k.y - (-base_line - (glyph.size.y - glyph.offsetY) * k.y) );
			
			++lineLength;
		}
		//dim.y = k.y;
	}
	
/*
=================================================
	_GetFormattedText
=================================================
*/
	template <typename GlyphReader>
	inline
	void IFont::_GetFormattedText (	const ViewportPtr &vp,
									const GlyphReader &glyphReader,
									float maxLineWidth,
									const FontBatch &params,
									const ext_string &extText,
									const LocaleCharacters &localeChars,
									OUT ext_string &formattedText,
									OUT vec2 &dim )
	{
		dim	= vec2();

		glyphReader.ScaleChanged( params._scale );

		formattedText.ClearMem();
		formattedText.Reserve( Round( extText.Count() * 1.1f ) );

		float		line_width	= 0.0f;
		int			line_align	= 0;

		vec2 const	base_scale	= vp->ViewRect().GetSize().y * params._scale * vp->GetResCorrectAspect();
		vec2		k			= base_scale;
		float		spacing		= params._spacing;
		float const	base_line	= glyphReader.GetBaseLineTopMargin() * k.y;
		float		hor_spacing	= k.y * (1.0f + spacing);
		
		bvec2 const	dir			= ivec2( params._direction & ETextDirection::_HORIZONTAL_MASK,
										 params._direction & ETextDirection::_VERTICAL_MASK ) ==
								  ivec2( ETextDirection::LEFT_TO_RIGHT, ETextDirection::TOP_TO_BOTTOM );

		k.x = SetSign( k.x, not dir.x );
		
		ext_string::TReader		reader	= extText.GetReader();
		ext_string::TReader		last_line_reader = formattedText.GetReader();

		while ( reader.NextChunk() != ext_string::END )
		{
			ext_string::TReader	tmp( reader );

			switch ( reader.GetState() )
			{
				case ext_string::COLOR :
					break;
				
				case ext_string::SCALE : {
					float	new_scale	= reader.GetChunkFloat();
					k					= base_scale * new_scale;
					hor_spacing			= k.y * (1.0f + spacing);
					glyphReader.ScaleChanged( params._scale * new_scale );
					break;
				}
				case ext_string::SPACING :
					spacing		= reader.GetChunkFloat();
					hor_spacing = k.y * (1.0f + spacing);
					break;

				case ext_string::OFFSET_X :
					line_width += reader.GetChunkFloat() * k.x;
					break;

				case ext_string::OFFSET_Y :
					dim.y += reader.GetChunkFloat() * k.y;
					break;

				case ext_string::STRING :
				{
					uni_string	text			= reader.GetString();

					// for word wrap
					usize		last_word		= 0;
					float		last_line_width	= 0.0f;

					usize		last_line		= 0;
					float		glyph_width		= 0.0f;
					
					for (usize i = 0; i < text.Length(); ++i)
					{
						uint8 j = (uint8)text[i];
			
						if ( IsNewLine( text, i ) )
						{
							usize	t	= i+1;

							while ( IsNewLine( text, t ) )
							{
								i = t++;
								dim.y += hor_spacing;
							}

							const isize	len = t - last_line;

							if ( len > 0 )
							{
								LineAlign( formattedText, last_line_reader, maxLineWidth, line_width, line_align, 1.0f / k.x );

								formattedText.Append( uni_c_string( text.cstr() + last_line, len ) );
								last_line_reader.MoveToEnd();
							}
							last_line		 = i+1;
							last_word		 = last_line;
							last_line_width	 = 0.0f;

							dim.x			 = Max( dim.x, line_width );
							dim.y			+= hor_spacing;
							line_width		 = 0.0f;
							continue;
						}

						if ( not glyphReader.Load( j ) )
						{
							WARNING( "Char is out of range!" );
							continue;
						}

						if ( localeChars.CanWrapCurrent( text, i ) )
						{
							last_word		= i;
							last_line_width = line_width;
						}

						Glyph const &	glyph = glyphReader.Get( j );
			
						glyph_width = ( glyph.offsetX[0] + glyph.size.x + glyph.offsetX[1] ) * k.x;
						line_width += glyph_width;


						// word wrap
						if ( line_width > maxLineWidth )
						{
							if ( last_word > last_line )
							{
								isize	len = last_word - last_line;

								LineAlign( formattedText, last_line_reader, maxLineWidth, last_line_width, line_align, 1.0f / k.x );

								formattedText.Append( uni_c_string( text.cstr() + last_line, len ), uni_c_string( "\n" ) );
								last_line_reader.MoveToEnd();

								last_line		 = GetNextSymbol( text, last_word );
								dim.x			 = Max( dim.x, last_line_width );
								dim.y			+= hor_spacing;
								line_width		 = line_width - last_line_width + isize(last_word - last_line) * glyph_width;
								last_line_width	 = 0.0f;
							}
							else
							{
								isize	len = i - last_line;

								line_width -= glyph_width;

								if ( len > 0 )
								{
									string str = uni_c_string( text.cstr() + last_line, len );

									LineAlign( formattedText, last_line_reader, maxLineWidth, line_width, line_align, 1.0f / k.x );

									formattedText.Append( uni_c_string( text.cstr() + last_line, len ), uni_c_string( "\n" ) );
									last_line_reader.MoveToEnd();
								}

								last_line		 = i;
								dim.x			 = Max( dim.x, line_width );
								dim.y			+= hor_spacing;
								line_width		 = glyph_width;
								last_line_width	 = 0.0f;
							}
						}
					}

					isize	len = text.Length() - last_line;
					
					if ( len > 0 )
					{
						LineAlign( formattedText, last_line_reader, maxLineWidth, line_width, line_align, 1.0f / k.x );
						formattedText.Append( uni_c_string( text.cstr() + last_line, len ) );
					}
					break;
				}

				case ext_string::ALIGN : {
					line_align	= reader.GetChunkInt();
					dim.x		= maxLineWidth;
					break;
				}

				default :
					WARNING( "Unknown chunk!" );
			}

			if ( tmp.GetState() != ext_string::STRING and
				 tmp.GetState() != ext_string::ALIGN  and
				 tmp.GetState() != ext_string::END )
			{
				formattedText.AppendChunk( tmp );
			}
		}
		
		dim.x  = Max( dim.x, line_width );
		dim.y += k.y;

		ASSERT( dim.x <= maxLineWidth );
	}

/*
=================================================
	_CalcDefaultScale
=================================================
*/
	inline float IFont::_CalcDefaultScale (const ViewportPtr &vp, uint heightInPixels)
	{
		const float	k  = vp->ViewRect().GetSize().y * vp->GetResCorrectAspect().y * vp->Size().y;

		return heightInPixels / k;
	}


}	// Graphics
}	// Engine